Skip to content

feat: support needs[].parallel.matrix and matrix expressions - #1848

Merged
firecow merged 1 commit into
firecow:masterfrom
inistor:feat/needs-parallel-matrix
May 9, 2026
Merged

feat: support needs[].parallel.matrix and matrix expressions#1848
firecow merged 1 commit into
firecow:masterfrom
inistor:feat/needs-parallel-matrix

Conversation

@inistor

@inistor inistor commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for two related GitLab CI features for parallel-matrix jobs:

  1. Static needs[].parallel.matrix selectors — pick specific producer permutations to depend on (closes Implement needs[].parallel.matrix #843). Shipped upstream in GitLab 16.3 (gitlab-org/gitlab!118839).

  2. Matrix expressions $[[ matrix.IDENTIFIER ]] inside selectors — resolved per consumer permutation, enabling 1:1 dependency mapping across stages. Shipped upstream as Beta in GitLab 18.6, GA in 18.8 (gitlab-org/gitlab!205247 + flag removal in !209773).

The original schema already permits needs[].parallel (refs parallel_matrix), so no schema patch is needed.

What works

Layer 1 — static selectors

  • Full selectors, e.g. parallel.matrix: [{NAME: foo}] selects only build-job: [foo]
  • Array-valued selectors expand to a cartesian product, e.g. [{NAME: [foo, bar]}]
  • Multi-key entries
  • optional: true skips silently when the selector matches zero permutations

Layer 2 — matrix expressions

  • Bare canonical form PROVIDER: $[[ matrix.PROVIDER ]] (matches the example in gitlab-org/gitlab#423553)
  • Array-wrapped form PROVIDER: ['$[[ matrix.PROVIDER ]]']
  • Multiple expressions concatenated in one string, e.g. TAG: build-$[[ matrix.OS ]]-$[[ matrix.STACK ]]
  • Identifiers with letters, digits, underscore, and hyphen (matches upstream's [a-zA-Z0-9_-]+ regex in MatrixInterpolator)
  • Whitespace variations inside $[[ ... ]]
  • Mixed entries (some keys literal, some expression-based)

Errors

  • Producer without parallel:matrix (including plain parallel: <int>)
  • Selector matching zero producer permutations on a non-optional need
  • $[[ matrix.X ]] in a consumer that is not parallel-matrix
  • $[[ matrix.X ]] referencing an identifier the consumer's matrix doesn't define — wording mirrors upstream's 'X' does not exist in matrix configuration for log grep parity

Tests

Six new fixtures under tests/test-cases/, 18 integration tests total:

Fixture Cases
needs-parallel-matrix-static single, array, partial, multi-key, optional+zero
needs-parallel-matrix-error producer without parallel:matrix, plain parallel: <int> producer, zero-match non-optional
needs-parallel-matrix-expressions canonical bare form, second permutation, array form, mixed literal+expression, hyphenated identifier, multi-expression concat, whitespace variation
needs-parallel-matrix-expressions-non-parallel-consumer parser-time rejection
needs-parallel-matrix-expressions-unknown-identifier parser-time rejection
needs-parallel-matrix-artifacts end-to-end artifact cascade (positive: matched producer's artifact arrives; negative: other permutations' artifacts do not)

The matrix-expressions fixtures mirror the cases in upstream's spec/lib/gitlab/ci/config/interpolation/matrix_interpolator_spec.rb adapted to gitlab-ci-local's integration-test infrastructure.

Behavior note: partial selectors

The original #254821 issue description marked partial selectors (selector mentions only a subset of the producer's matrix keys) as "out of scope". This implementation supports them: a selector key list is treated as a constraint, with the producer's unmentioned keys free to take any value. This is also the behavior implied by upstream's matrix-expression test fixtures (which show partial-key shapes working) and is the more useful pattern when combined with $[[ matrix.X ]] substitution.

Happy to tighten this if you'd prefer strict full-selector semantics — let me know.

Out of scope (follow-ups if requested)

  • Interaction with spec: headers / inputs expressions (separate code path that already lives in this project)
  • Numeric matrix values are accepted by String(v) coercion but lack an explicit fixture
  • extends: propagation of needs[].parallel.matrix (likely works via existing deepExtend, untested)

References

Test plan

  • bun run typecheck
  • bun run lint
  • bunx vitest run tests/test-cases/needs-parallel-matrix-*
  • CI green (already verified via internal fork PR before opening this one)

Acknowledgment

This PR was implemented with assistance from Claude (Anthropic) as a pair-programming tool. The implementation was audited against upstream gitlab-org/gitlab MRs and test files (notably !118839 and !205247) to ensure behavior parity with GitLab Runner's own logic. All code and tests were reviewed by me before pushing; CI on the internal fork PR confirmed the full vitest suite and four Node/Bun smoke matrix variants pass on Linux. Co-authorship is preserved in the commit trailer.


Summary by cubic

Adds support for needs[].parallel.matrix selectors and $[[ matrix.X ]] expressions. This enables precise, 1:1 dependency mapping across parallel.matrix jobs and ensures artifacts flow only from matched permutations.

  • New Features
    • Static selectors: choose specific producer permutations. Supports arrays (cartesian), multi-key and partial selectors, and optional: true for zero matches.
    • Matrix expressions: resolve $[[ matrix.IDENTIFIER ]] per consumer permutation. Supports array-wrapped values, multiple expressions in one string, hyphenated identifiers, and whitespace variations.
    • Validation: errors if producer is not parallel:matrix (including parallel:), if a non-optional selector matches nothing, if a non-parallel consumer uses expressions, or if an expression references an unknown identifier.
    • Artifacts: consumers pull artifacts only from matched producer permutations.

Written for commit 772dfaf. Summary will update on new commits.

Implements two related GitLab CI features for jobs whose producer or
consumer uses parallel.matrix, mirroring upstream gitlab-org/gitlab
behavior (MRs !118839 for the static layer, !205247 for matrix
expressions):

1. Static needs[].parallel.matrix selectors (closes firecow#843):
   pick specific producer permutations to depend on, instead of all of
   them. Supports partial selectors (mention a subset of the producer's
   matrix keys), array-valued selectors (cartesian expansion), and
   honors `optional: true` when the selector matches zero permutations.

2. Matrix expressions ($[[ matrix.IDENTIFIER ]]) inside selectors,
   resolved per consumer permutation, enabling 1:1 dependency mapping
   between matched permutations. Supports the bare canonical form
   (`X: $[[ matrix.X ]]`) and array-wrapped form, multiple expressions
   concatenated in one string, identifiers containing letters, digits,
   underscore, and hyphen (matching upstream's `[a-zA-Z0-9_-]+`
   charset), and whitespace variations inside `$[[ ... ]]`.

Errors are surfaced clearly for: producer without parallel:matrix
(including plain `parallel: <int>`), selector matching zero
permutations on a non-optional need, $[[ matrix.X ]] in a consumer
that is not parallel:matrix, and $[[ matrix.X ]] referencing an
identifier the consumer's matrix doesn't define (error wording
mirrors upstream's "'<X>' does not exist in matrix configuration").

Tests cover the canonical example from gitlab-org/gitlab#423553
verbatim plus the upstream RSpec cases from
spec/lib/gitlab/ci/config/interpolation/matrix_interpolator_spec.rb
adapted to gitlab-ci-local's integration-test infrastructure.

See https://docs.gitlab.com/ci/yaml/#needsparallelmatrix and
https://docs.gitlab.com/ci/yaml/matrix_expressions/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 18 files

@firecow firecow left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff: implements needs[].parallel.matrix selectors and $[[ matrix.X ]] expressions to match upstream GitLab semantics (16.3, 18.6/18.8). Helpers cleanly isolated in src/parallel.ts, error messages mirror upstream wording, regex charset matches upstream's MatrixInterpolator, test coverage covers the artifact cascade and four explicit error paths. LGTM.

@firecow
firecow merged commit 5898e76 into firecow:master May 9, 2026
14 checks passed
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request May 13, 2026
This MR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Adoption](https://docs.renovatebot.com/merge-confidence/) | [Passing](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [npm:gitlab-ci-local](https://github.com/firecow/gitlab-ci-local) | `4.71.0` → `4.72.0` | ![age](https://developer.mend.io/api/mc/badges/age/npm/gitlab-ci-local/4.72.0?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/gitlab-ci-local/4.72.0?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/gitlab-ci-local/4.71.0/4.72.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/gitlab-ci-local/4.71.0/4.72.0?slim=true) |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>firecow/gitlab-ci-local (npm:gitlab-ci-local)</summary>

### [`v4.72.0`](https://github.com/firecow/gitlab-ci-local/releases/tag/4.72.0)

[Compare Source](firecow/gitlab-ci-local@4.71.0...4.72.0)

#### What's Changed

- feat: support spec:inputs for root pipeline via --input and --inputs-file by [@&#8203;gyanranjan](https://github.com/gyanranjan) in [#&#8203;1814](firecow/gitlab-ci-local#1814)
- feat: implement workflow:rules:variables support ([#&#8203;1832](firecow/gitlab-ci-local#1832)) by [@&#8203;bcouetil](https://github.com/bcouetil) in [#&#8203;1833](firecow/gitlab-ci-local#1833)
- chore(deps): lock file maintenance by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1834](firecow/gitlab-ci-local#1834)
- chore(deps): update all non-major by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1835](firecow/gitlab-ci-local#1835)
- fix: pass artifact paths to rsync via `--files-from`  by [@&#8203;Paul-Goulpie](https://github.com/Paul-Goulpie) in [#&#8203;1825](firecow/gitlab-ci-local#1825)
- chore(deps): update sonarsource/sonarqube-scan-action action to v8 by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1842](firecow/gitlab-ci-local#1842)
- fix: connect service containers to local registry network by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1847](firecow/gitlab-ci-local#1847)
- test: ignore 'still running' heartbeat in stdout assertions by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1851](firecow/gitlab-ci-local#1851)
- chore(deps): update github/codeql-action action to v4.35.4 by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1843](firecow/gitlab-ci-local#1843)
- chore(deps): lock file maintenance by [@&#8203;renovate](https://github.com/renovate)\[bot] in [#&#8203;1845](firecow/gitlab-ci-local#1845)
- feat: add environment column to --list and --list-csv ([#&#8203;1837](firecow/gitlab-ci-local#1837)) by [@&#8203;bcouetil](https://github.com/bcouetil) in [#&#8203;1838](firecow/gitlab-ci-local#1838)
- feat: support needs\[].parallel.matrix and matrix expressions by [@&#8203;inistor](https://github.com/inistor) in [#&#8203;1848](firecow/gitlab-ci-local#1848)
- fix: reject empty rules array instead of silently skipping job by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1852](firecow/gitlab-ci-local#1852)
- fix: respect IGNORE\_PREDEFINED\_VARS in .gitlab-ci-local-env by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1853](firecow/gitlab-ci-local#1853)
- fix: reject ${VAR} in rules:if by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1854](firecow/gitlab-ci-local#1854)
- fix: wait for child stdio close before resolving exec by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1855](firecow/gitlab-ci-local#1855)
- chore: pin third-party actions to commit SHAs by [@&#8203;firecow](https://github.com/firecow) in [#&#8203;1857](firecow/gitlab-ci-local#1857)

#### New Contributors

- [@&#8203;gyanranjan](https://github.com/gyanranjan) made their first contribution in [#&#8203;1814](firecow/gitlab-ci-local#1814)
- [@&#8203;inistor](https://github.com/inistor) made their first contribution in [#&#8203;1848](firecow/gitlab-ci-local#1848)

**Full Changelog**: <firecow/gitlab-ci-local@4.71.0...4.72.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzMuNSIsInVwZGF0ZWRJblZlciI6IjQzLjE3My41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement needs[].parallel.matrix

2 participants